import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { PERMS_KEY } from '../../util/constants';
import { PermsType } from '../decorator/my.decorator';
import { UserRolesPermsVo } from '../../sys/admin/user/vo/user-roles-perms.vo';
import { RoleEnum } from '../../util/enum/role.enum';

@Injectable()
export class PermsGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredPerms = this.reflector.getAllAndOverride<PermsType>(
      PERMS_KEY,
      [context.getHandler(), context.getClass()],
    );
    // 如果没有@perms,就不需要权限
    if (!requiredPerms) {
      return true;
    }
    const user: UserRolesPermsVo = context.switchToHttp().getRequest()['user'];
    // 如果用户是ROOT,不需要权限
    if (user.roles.includes(RoleEnum.Root)) {
      return true;
    }
    const can = this.checkPerms(requiredPerms, user.perms);
    if (!can) {
      throw new UnauthorizedException('需要权限' + requiredPerms.toString());
    }
    return can;

    // 参考 https://www.npmjs.com/package/express-jwt-permissions
    // 'read' Required: "read"
    // ['read', 'write']  Required: "read" AND "write"
    //  [ ['admin'], ['read', 'write']]  Required: "admin" OR ("read" AND "write")
  }

  /**
   * 验证权限
   * @param requiredPerms 需要的权限
   * @param userPerms 用户拥有的权限
   * @private
   */
  private checkPerms(requiredPerms: PermsType, userPerms: string[]): boolean {
    // 比如 'read'
    if (typeof requiredPerms === 'string') {
      return userPerms.includes(requiredPerms);
    } else if (Array.isArray(requiredPerms) && requiredPerms.length > 0) {
      // 比如 ['read', 'write']
      if (typeof requiredPerms[0] === 'string') {
        return (requiredPerms as string[]).every((perm: string) =>
          userPerms.includes(perm),
        );
      } else {
        // 比如 [ ['admin'], ['read', 'write']]
        return (requiredPerms as string[][]).some((perm: string[]) =>
          this.checkPerms(perm, userPerms),
        );
      }
    } else {
      return false;
    }
  }
}
